package lang

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"bytes"
)
//example AST for:
/*
func mul(a, b) {
	return a * b
}

func main() {
	a = mul(1, 2)
	b = mul(a, 2)
	c = mul(mul(a, b), 2)
	return c
}
--------------------------
NODEGROUP
--DEFUN: mul
----ARGLIST
------IDENT: a
------IDENT: b
----NODEGROUP
------RETURN
--------BINOP: mul
----------IDENT: a
----------IDENT: b

--DEFUN: main
----ARGLIST
----NODEGROUP
------VARDEF(a)
--------CALL(mul)
----------NUMBER: 1
----------NUMBER: 2
------VARDEF(b)
--------CALL(mul)
----------IDENT: a
----------NUMBER: 2
------VARDEF(c)
--------CALL(mul)
----------CALL(mul)
-----------IDENT: a
-----------IDENT: b
----------NUMBER: 2
------RETURN
--------IDENT: c
*/

type Node interface {
	Type() NodeType
	String() string
}

type NodeType int

func (t NodeType) Type() NodeType {
	return t
}

const (
	NodeGroup   NodeType = iota // group of nodes
	NodeVarDef                  // variable definition
	NodeIdent                   // identifier
	NodeBool                    // boolean literal
	NodeNumber                  // number literal
	NodeStr                     // string literal
	NodeRStr                    // raw string literal
	NodeArgList                 // argument list definition XXX do we need this or is it internal
	NodeDefun                   // function definition
	NodeReturn                  // return statement
	NodeBinOp                   // binary operation
	NodeCall                    // function call
	NodeBranch                  // if statement
	NodeLoop                    // loop of some sort
)

type GroupNode struct {
	NodeType
	Nodes []Node //XXX should be *node?
}

func newGroup() *GroupNode {
	return &GroupNode{NodeType: NodeGroup}
}

func (g *GroupNode) Append(n Node) {
	g.Nodes = append(g.Nodes, n)
}

func (g *GroupNode) String() string {
	b := new(bytes.Buffer)
	fmt.Fprint(b, "NODEGROUP[")
	for _, n := range g.Nodes {
		fmt.Fprintf(b, "%s", n)
	}
	fmt.Fprint(b, "]")
	return b.String()
}

type VarDefNode struct {
	NodeType
	Ident string
	Val   Node
}

func newVarDef(ident string, val Node) *VarDefNode {
	return &VarDefNode{NodeType: NodeVarDef, Ident: ident, Val: val}
}

func (v *VarDefNode) String() string {
	return fmt.Sprintf("V=%s|%s", v.Ident, v.Val)
}

type IdentNode struct {
	NodeType
	Ident string
}

func newIdent(ident string) *IdentNode {
	return &IdentNode{NodeType: NodeIdent, Ident: ident}
}

func (i *IdentNode) String() string {
	return fmt.Sprintf("I=%q", i.Ident)
}

type BoolNode struct {
	NodeType
	Val bool
}

func newBool(val bool) *BoolNode {
	return &BoolNode{NodeType: NodeBool, Val: val}
}

func (b *BoolNode) String() string {
	return fmt.Sprintf("B=%t", b.Val)
}

type NumberNode struct {
	NodeType
	IsInt   bool
	IsUint  bool
	IsFloat bool
	Int64   int64
	Uint64  uint64
	Float64 float64
	Text    string
}

func newNumber(text string) (*NumberNode, os.Error) {
	n := &NumberNode{NodeType: NodeNumber, Text: text}
	u, err := strconv.Btoui64(text, 0)
	if err == nil {
		n.IsUint = true
		n.Uint64 = u
	}
	i, err := strconv.Btoi64(text, 0)
	if err == nil {
		n.IsInt = true
		n.Int64 = i
		if i == 0 {
			n.IsUint = true
			n.Uint64 = u
		}
	}
	if n.IsInt {
		n.IsFloat = true
		n.Float64 = float64(n.Int64)
	} else if n.IsUint {
		n.IsFloat = true
		n.Float64 = float64(n.Uint64)
	} else {
		f, err := strconv.Atof64(text)
		if err == nil {
			n.IsFloat = true
			n.Float64 = f
			if !n.IsInt && float64(int64(f)) == f {
				n.IsInt = true
				n.Int64 = int64(f)
			}
			if !n.IsUint && float64(uint64(f)) == f {
				n.IsUint = true
				n.Uint64 = uint64(f)
			}
		}
	}

	if !n.IsInt && !n.IsUint && !n.IsFloat {
		return nil, fmt.Errorf("illegal number syntax: %q", text)
	}
	return n, nil
}

func (n *NumberNode) String() string {
	return fmt.Sprintf("N=%s", n.Text)
}

type StrNode struct {
	NodeType
	Val string
}

func newStr(val string) *StrNode {
	nval, err := strconv.Unquote(val)
	if err != nil {
		panic(err)
	}
	return &StrNode{NodeType: NodeStr, Val: nval}
}

func (s *StrNode) String() string {
	return fmt.Sprintf("S=%q", s.Val)
}

type RStrNode struct {
	NodeType
	Val string
}

func newRStr(val string) *RStrNode {
	//we have to do this ourselves because Go is annoying
	/*quote := val[0]
	n := len(val)
	if quote != val[n-1] {
		panic("Bad raw string")
	}
	val = val[1 : n-1]

	var buf bytes.Buffer
	for len(val) > 0 {
		c, multibyte, ss, err := strconv.UnquoteChar(val, quote)
		if err != nil {
			panic(err)
		}
		val = ss
		if c < utf8.RuneSelf || !multibyte {
			buf.WriteByte(byte(c))
		} else {
			buf.WriteString(string(c))
		}
	}
	nval := buf.String()*/
	//rather than dealing with this shit, let's just assume the lexer did it's magic for us correctly
	//FIXME this is a hack
	nval := strings.Replace(val, "\\'", "'", -1)
	return &RStrNode{NodeType: NodeRStr, Val: nval[1 : len(nval)-1]}
}

func (s *RStrNode) String() string {
	return fmt.Sprintf("RS=%q", s.Val)
}

type ArgListNode struct {
	NodeType
	//Args []Node
	Args *GroupNode
}

func newArgList(args *GroupNode) *ArgListNode {
	return &ArgListNode{NodeType: NodeArgList, Args: args}
}

func (a *ArgListNode) String() string {
	return fmt.Sprintf("AL=%s", a.Args)
}

type DefunNode struct {
	NodeType
	Name string
	Args *GroupNode
	Body *GroupNode
}

func newDefun(name string, args *GroupNode, body *GroupNode) *DefunNode {
	return &DefunNode{NodeType: NodeDefun, Name: name, Args: args, Body: body}
}

func (d *DefunNode) String() string {
	return fmt.Sprintf("DEFUN=%s | %s | %s", d.Name, d.Args, d.Body)
}

type ReturnNode struct {
	NodeType
	Val Node //XXX pointer?
}

func newReturn(val Node) *ReturnNode {
	return &ReturnNode{NodeType: NodeReturn, Val: val}
}

func (r *ReturnNode) String() string {
	return fmt.Sprintf("R=%s", r.Val)
}

type BinOp int

const ( // in order of precedence
	NotOp BinOp = iota
	MulOp
	DivOp
	PlusOp
	MinOp
	NeqOp
	GTOp
	LTOp
	GTEOp
	LTEOp
	EqOp
	AndOp
	OrOp
)
//TODO XXX FIXME (URGENT) Rename this. It's not binop
var binOp = map[BinOp]string{
	//unary
	NotOp: "!",
	//binary
	PlusOp: "+",
	MinOp:  "-",
	MulOp:  "*",
	DivOp:  "/",
	//logic
	NeqOp: "!=",
	GTOp:  ">",
	LTOp:  "<",
	GTEOp: ">=",
	LTEOp: "<=",
	AndOp: "&&",
	OrOp:  "||",
	EqOp:  "==",
}

var binOps = map[itemType]BinOp{
	itemLNot:  NotOp,
	itemPlus:  PlusOp,
	itemMinus: MinOp,
	itemMult:  MulOp,
	itemDiv:   DivOp,
	itemLAnd:  AndOp,
	itemLOr:   OrOp,
	itemLEq:   EqOp,
	itemLNeq:  NeqOp,
	itemLGT:   GTOp,
	itemLGTE:  GTEOp,
	itemLLT:   LTOp,
	itemLLTE:  LTEOp,
}

var preced = map[BinOp]int{
	//unary are all 100
	NotOp: 100,
	//binary
	MulOp:  60,
	DivOp:  60,
	PlusOp: 30,
	MinOp:  30,
	//logic
	GTOp:  25,
	GTEOp: 25,
	LTOp:  25,
	LTEOp: 25,
	EqOp:  20, //equals must come before any logic but after any real binops or unaries
	NeqOp: 20,
	AndOp: 15, //ands are evalutated first dipshit
	OrOp:  10,
}

func (b BinOp) String() string {
	return binOp[b]
}

func getBinOp(i item) BinOp {
	op, ok := binOps[i.typ]
	if !ok {
		panic("bad binop?")
	}
	return op
}

type BinOpNode struct {
	NodeType
	Op  BinOp
	LHS Node
	RHS Node
}

func newBinOp(op BinOp, lhs, rhs Node) *BinOpNode {
	return &BinOpNode{NodeType: NodeBinOp, LHS: lhs, RHS: rhs, Op: op}
}

func (b *BinOpNode) String() string {
	return fmt.Sprintf("BO=%s|%s|%s", b.LHS, b.Op, b.RHS)
}

type CallNode struct {
	NodeType
	Target string
	Args   *ArgListNode
}

func newCall(target string, args *ArgListNode) *CallNode {
	return &CallNode{NodeType: NodeCall, Target: target, Args: args}
}

func (c *CallNode) String() string {
	return fmt.Sprintf("CALL=%s|%s", c.Target, c.Args)
}

type BranchNode struct {
	NodeType
	True  *GroupNode
	False *GroupNode
	Cond  Node
}

func newBranch(tr, fl *GroupNode, cnd Node) *BranchNode {
	return &BranchNode{NodeType: NodeBranch, True: tr, False: fl, Cond: cnd}
}

func (b *BranchNode) String() string {
	return fmt.Sprintf("BRNCH=%s|%s|%s", b.Cond, b.True, b.False)
}

type LoopNode struct {
	NodeType
	Pre  Node //for for loops
	Cond Node
	Post Node //for for loops
	Body *GroupNode
}

func newLoop(pre, cond, post Node, body *GroupNode) *LoopNode {
	return &LoopNode{NodeType: NodeLoop, Pre: pre, Cond: cond, Post: post, Body: body}
}

func (l *LoopNode) String() string {
	return fmt.Sprintf("LOOP=%s|%s|%s::::%s", l.Pre, l.Cond, l.Post, l.Body)
}
